/*
 * Mat4.cpp.inl
 *
 *  Created on: 20.09.2009
 *  Author: Christoph Caks
 */

// this file is included by its header file to satisfy the compiler

template <typename Real>
Mat4<Real>::Mat4() {
	for ( int i = 0; i < 16; i++ ) {
		data[i] = 0;
	}
}

template <typename Real>
Mat4<Real>::Mat4( 	const Real r00, const Real r01, const Real r02, const Real r03,
					const Real r10, const Real r11, const Real r12, const Real r13,
					const Real r20, const Real r21, const Real r22, const Real r23,
					const Real r30, const Real r31, const Real r32, const Real r33 ) {
	this->data[0] = r00;
	this->data[1] = r01;
	this->data[2] = r02;
	this->data[3] = r03;

	this->data[4] = r10;
	this->data[5] = r11;
	this->data[6] = r12;
	this->data[7] = r13;

	this->data[8] = r20;
	this->data[9] = r21;
	this->data[10] = r22;
	this->data[11] = r23;

	this->data[12] = r30;
	this->data[13] = r31;
	this->data[14] = r32;
	this->data[15] = r33;
}

template <typename Real>
Mat4<Real>::Mat4( const Real matrix[16] ) {
	for ( int i = 0; i < 16; i++ ) {
		this->data[i] = matrix[i];
	}
}

template <typename Real>
Mat4<Real>::Mat4( const Mat4& m4 ) {
	for ( int i = 0; i < 16; i++ ) {
		this->data[i] = m4.data[i];
	}
}

template <typename Real>
Mat4<Real>::~Mat4() {
}

template <typename Real>
const Real* Mat4<Real>::operator [] ( int iRow ) const {
	return &data[iRow*4];
}

template <typename Real>
Real* Mat4<Real>::operator [] ( int iRow ) {
	return &data[iRow*4];
}

template <typename Real>
Real Mat4<Real>::operator () ( int iRow, int iCol ) const {
	return data[iRow*4+iCol];
}

template <typename Real>
Real& Mat4<Real>::operator () ( int iRow, int iCol ) {
	return data[iRow*4+iCol];
}

template <typename Real>
void Mat4<Real>::setRow( int iRow, const Vec4<Real>& v4Val ) {
	data[iRow*4 + 0] = v4Val[0];
	data[iRow*4 + 1] = v4Val[1];
	data[iRow*4 + 2] = v4Val[2];
	data[iRow*4 + 3] = v4Val[3];
}

template <typename Real>
void Mat4<Real>::setCol( int iCol, const Vec4<Real>& v4Val ) {
	data[0*4 + iCol] = v4Val[0];
	data[1*4 + iCol] = v4Val[1];
	data[2*4 + iCol] = v4Val[2];
	data[3*4 + iCol] = v4Val[3];
}

template <typename Real>
Vec4<Real> Mat4<Real>::getRow( int iRow ) const {
	return Vec4<Real>( data[iRow*4 + 0], data[iRow*4 + 1], data[iRow*4 + 2], data[iRow*4 + 3] );
}

template <typename Real>
Vec4<Real> Mat4<Real>::getCol( int iCol ) const {
	return Vec4<Real>( data[0*4 + iCol], data[1*4 + iCol], data[2*4 + iCol], data[3*4 + iCol] );
}

template <typename Real>
void Mat4<Real>::setZero() {
	for ( int i = 0; i < 16; i++ ) {
		data[i] = 0;
	}
}

template <typename Real>
void Mat4<Real>::setIdentity() {
	data[0] = 1;
	data[1] = 0;
	data[2] = 0;
	data[3] = 0;

	data[4] = 0;
	data[5] = 1;
	data[6] = 0;
	data[7] = 0;

	data[8] = 0;
	data[9] = 0;
	data[10] = 1;
	data[11] = 0;

	data[12] = 0;
	data[13] = 0;
	data[14] = 0;
	data[15] = 1;
}

template <typename Real>
Mat4<Real>& Mat4<Real>::operator = ( const Mat4<Real>& m4Rhs ) {
	for ( int i = 0; i < 16; i++ ) {
		data[i] = m4Rhs.data[i];
	}
}

// arithmetic operations
template <typename Real>
Mat4<Real> Mat4<Real>::operator + ( const Mat4<Real>& mRhs ) const {
	return Mat4<Real>( 	data[0] + mRhs.data[0], 	data[1] + mRhs.data[1], 	data[2] + mRhs.data[2], 	data[3] + mRhs.data[3],
						data[4] + mRhs.data[4], 	data[5] + mRhs.data[5], 	data[6] + mRhs.data[6], 	data[7] + mRhs.data[7],
						data[8] + mRhs.data[8], 	data[9] + mRhs.data[9], 	data[10] + mRhs.data[10], 	data[11] + mRhs.data[11],
						data[12] + mRhs.data[12], 	data[13] + mRhs.data[13], 	data[14] + mRhs.data[14], 	data[15] + mRhs.data[15] );
}

template <typename Real>
Mat4<Real> Mat4<Real>::operator - ( const Mat4<Real>& mRhs ) const {
	return Mat4<Real>( 	data[0] - mRhs.data[0], 	data[1] - mRhs.data[1], 	data[2] - mRhs.data[2], 	data[3] - mRhs.data[3],
						data[4] - mRhs.data[4], 	data[5] - mRhs.data[5], 	data[6] - mRhs.data[6], 	data[7] - mRhs.data[7],
						data[8] - mRhs.data[8], 	data[9] - mRhs.data[9], 	data[10] - mRhs.data[10], 	data[11] - mRhs.data[11],
						data[12] - mRhs.data[12], 	data[13] - mRhs.data[13], 	data[14] - mRhs.data[14], 	data[15] - mRhs.data[15] );
}

template <typename Real>
Mat4<Real> Mat4<Real>::operator * ( const Real rRhs ) const {
	return Mat4<Real>(	data[0] * rRhs, data[1] * rRhs, data[2] * rRhs, data[3] * rRhs,
						data[4] * rRhs, data[5] * rRhs,	data[6] * rRhs, data[7] * rRhs,
						data[8] * rRhs, data[9] * rRhs, data[10] * rRhs, data[11] * rRhs,
						data[12] * rRhs, data[13] * rRhs, data[14] * rRhs, data[15] * rRhs );
}

template <typename Real>
Mat4<Real> Mat4<Real>::operator / ( const Real rRhs ) const {
	return Mat4<Real>(	data[0] / rRhs, data[1] / rRhs, data[2] / rRhs, data[3] / rRhs,
						data[4] / rRhs, data[5] / rRhs,	data[6] / rRhs, data[7] / rRhs,
						data[8] / rRhs, data[9] / rRhs, data[10] / rRhs, data[11] / rRhs,
						data[12] / rRhs, data[13] / rRhs, data[14] / rRhs, data[15] / rRhs );
}

template <typename Real>
Mat4<Real> Mat4<Real>::operator * ( const Mat4<Real>& m4Rhs ) const {
	Mat4<Real> m4RetVal;
	m4RetVal.setZero();
	for ( int column = 0; column < 4; column++ ) {
		for ( int row = 0; row < 4; row++ ) {
			for ( int count = 0; count < 4; count ++ ) {
				m4RetVal.data[ column + (4 * row) ] += this->data[ row * 4 + count ] * m4Rhs.data[ count * 4 + column];
			}
		}
	}
	return m4RetVal;
}


template <typename Real>
Mat4<Real> Mat4<Real>::operator += ( const Mat4<Real>& mRhs ) {
	for ( int i = 0; i < 16; i++ ) {
		data[i] += mRhs.data[i];
	}
}

template <typename Real>
Mat4<Real> Mat4<Real>::operator -= ( const Mat4<Real>& mRhs ) {
	for ( int i = 0; i < 16; i++ ) {
		data[i] -= mRhs.data[i];
	}
}

template <typename Real>
Mat4<Real> Mat4<Real>::operator *= ( const Real rRhs ) {
	for ( int i = 0; i < 16; i++ ) {
		data[i] *= rRhs;
	}
}

template <typename Real>
Mat4<Real> Mat4<Real>::operator /= ( const Real rRhs ) {
	for ( int i = 0; i < 16; i++ ) {
		data[i] /= rRhs;
	}
}

template <typename Real>
Mat4<Real> Mat4<Real>::operator *= ( const Mat4<Real>& m4Rhs ) {
	Mat4<Real> m4RetVal;
	m4RetVal.setZero();
	for ( int column = 0; column < 4; column++ ) {
		for ( int row = 0; row < 4; row++ ) {
			for ( int count = 0; count < 4; count ++ ) {
				m4RetVal.data[ column + (4 * row) ] += this->data[ row * 4 + count ] * m4Rhs.data[ count * 4 + column];
			}
		}
	}
	// TODO fix operator *= for mat * mat!
	for ( int i = 0; i < 16; i++ ) {
		this->data[i] = m4RetVal.data[i];
	}
	return m4RetVal;
}

// TODO comparison operations
template <typename Real>
bool Mat4<Real>::operator == ( const Mat4<Real>& m4Rhs ) const {
	return 	data[0] == m4Rhs.data[0] 	&& data[1] == m4Rhs.data[2] 	&& data[2] == m4Rhs.data[2] 	&& data[3] == m4Rhs.data[3] 	&&
			data[4] == m4Rhs.data[4] 	&& data[5] == m4Rhs.data[5] 	&& data[6] == m4Rhs.data[6] 	&& data[7] == m4Rhs.data[7] 	&&
			data[8] == m4Rhs.data[8] 	&& data[9] == m4Rhs.data[9] 	&& data[10] == m4Rhs.data[10] 	&& data[11] == m4Rhs.data[11] 	&&
			data[12] == m4Rhs.data[12] 	&& data[13] == m4Rhs.data[13] 	&& data[14] == m4Rhs.data[14] 	&& data[15] == m4Rhs.data[15];
}

template <typename Real>
bool Mat4<Real>::operator != ( const Mat4<Real>& m4Rhs ) const {
	return 	data[0] != m4Rhs.data[0] 	|| data[1] != m4Rhs.data[1] 	|| data[2] != m4Rhs.data[2] 	|| data[3] != m4Rhs.data[3] 	||
			data[4] != m4Rhs.data[4] 	|| data[5] != m4Rhs.data[5] 	|| data[6] != m4Rhs.data[6] 	|| data[7] != m4Rhs.data[7] 	||
			data[8] != m4Rhs.data[8] 	|| data[9] != m4Rhs.data[9] 	|| data[10] != m4Rhs.data[10] 	|| data[11] != m4Rhs.data[11] 	||
			data[12] != m4Rhs.data[12] 	|| data[13] != m4Rhs.data[13] 	|| data[14] != m4Rhs.data[14] 	|| data[15] != m4Rhs.data[15];
}

// TODO geometric operations

template <typename Real>
Vec4<Real> Mat4<Real>::operator * ( const Vec4<Real>& v4Rhs ) const {
	return Vec4<Real>( 	data[0] * v4Rhs.data[0] 	+ data[4] * v4Rhs.data[1] 	+ data[8] * v4Rhs.data[2] 	+ data[12] * v4Rhs.data[3],
						data[1] * v4Rhs.data[0] 	+ data[5] * v4Rhs.data[1] 	+ data[9] * v4Rhs.data[2] 	+ data[13] * v4Rhs.data[3],
						data[2] * v4Rhs.data[0] 	+ data[6] * v4Rhs.data[1] 	+ data[10] * v4Rhs.data[2] 	+ data[14] * v4Rhs.data[3],
						data[3] * v4Rhs.data[0] 	+ data[7] * v4Rhs.data[1] 	+ data[11] * v4Rhs.data[2] 	+ data[15] * v4Rhs.data[3] );
}

// other
template <typename Real>
Mat4<Real> Mat4<Real>::transpose() const {
	return Mat4<Real>(	data[0], data[4], data[8], data[12],
						data[1], data[5], data[9], data[13],
						data[2], data[6], data[10], data[14],
						data[3], data[7], data[11], data[15] );
}

template <typename Real>
Mat4<Real> Mat4<Real>::inverse() const {
	if ( data[12] == 0 && data[13] == 0 && data[14] == 0 && data[15] == 1 ) {
		Vec3<Real> u = Vec3<Real>( data[0], data[4], data[8] );
		Vec3<Real> v = Vec3<Real>( data[1], data[5], data[9] );
		Vec3<Real> w = Vec3<Real>( data[2], data[6], data[10] );
		Vec3<Real> t = Vec3<Real>( data[3], data[7], data[11] );
		
		return Mat4<Real>( 	data[0], data[4], data[8], -u.dot( t ),
							data[1], data[5], data[9], -v.dot( t ),
							data[2], data[6], data[10], -w.dot( t ),
							0, 0, 0, 1 );
	}
	else {
	// TODO implement general inverse
		return Mat4<Real>();
	}
}

template <typename Real>
Mat4<Real> Mat4<Real>::adjoint() const {
	// TODO implement adjoint
	return Mat4<Real>();
}

template <typename Real>
Real Mat4<Real>::determinant() const {
	// TODO implement determinant
	return Mat4<Real>();

}
